/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.lwuit.automation;

import com.sun.lwuit.*;
import com.sun.lwuit.animations.Animation;
import com.sun.lwuit.impl.LWUITImplementation;
import com.sun.lwuit.impl.LWUITImplementationDecorator;
import com.sun.lwuit.util.Log;
import java.util.Hashtable;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Allows us to extend the implementation with debugging and testing features
 *
 * @author Shai Almog
 */
class DebugImplementation extends LWUITImplementationDecorator {
    static final int POINTER_PRESSED = 1;
    static final int POINTER_RELEASED = 2;
    static final int POINTER_DRAGGED = 3;
    static final int KEY_PRESSED = 4;
    static final int KEY_RELEASED = 5;
    static final int KEY_LONG_PRESSED = 6;

    private Throwable edtException;
    private DebugController.Script recording;
    private DebugController.Script pausedRecording;
    
    private long startTime;
    static DebugImplementation instance;
    
    /**
     * Indicates the behavior of LWUIT when an EDT violation is detected (not all
     * edt violations can be detected!) this can be one of: EDT_VIOLATION_IGNORE,
     * EDT_VIOLATION_RUNTIME_EXCEPTION, EDT_VIOLATION_WARN
     */
    private int edtMode;
    
    private Hashtable longClickKeyCodeMap = new Hashtable();
    private Hashtable keyCodeMap = new Hashtable();
    
    private int lastKeyCode;
    private long pressTime;
    
    private float ratio = 1;

    private Hashtable gameKeyMap;
    
    private boolean drawRecordingText = true;
    
    private boolean dontRecordNextRelease;
    private boolean pressCharged;
    private long pauseDuration;
    
    public DebugImplementation(LWUITImplementation underlying) {
        super(underlying);
        // this timer task causes the painting to blink
        instance = this;
        Timer t = new Timer();
        t.schedule(new TimerTask() {
            private int counter;
            public void run() {
                if(recording != null || pausedRecording != null) {
                    counter++;
                    if(counter % 5 == 0) {
                        drawRecordingText = false;
                        Display.getInstance().getCurrent().repaint(10, 10, 100, 20);
                    } else {
                        drawRecordingText = true;

                        // prevent redundant repaints
                        if((counter - 1) % 5 == 0) {
                            Display.getInstance().getCurrent().repaint(10, 10, 100, 20);
                        }
                    }
                }
                // this is a long press...
                synchronized(longClickKeyCodeMap) {
                    if(pressCharged && System.currentTimeMillis() - pressTime >= 1000) {
                        dontRecordNextRelease = true;
                        pressCharged = false;
                        Runnable target = (Runnable)longClickKeyCodeMap.get(new Integer(lastKeyCode));
                        if(target != null) {
                            revertLastPressFromScript();
                            Display.getInstance().callSerially(target);
                        }
                    }
                }
            }
        }, 300, 300);
    }
    
    /**
     * @inheritDoc
     */
    public int getGameAction(int key) {
        if(gameKeyMap != null) {
            Integer i = (Integer)gameKeyMap.get(new Integer(key));
            if(i != null) {
                return i.intValue();
            }
        }
        int g = super.getGameAction(key);
        return g;
    }
    
    /**
     * Sets the ratio for playback of scripts, this defaults to 1 but can be slowed
     * down or sped up
     */
    void setPlaybackSpeed(float ratio) {
        this.ratio = ratio;
    }
    
    Throwable fetchEDTException() {
        Throwable ex = edtException;
        edtException = null;
        return ex;
    }
    
    void startRecording() {
        recording = new DebugController.Script();
        Display.getInstance().getCurrent().repaint();
        startTime = System.currentTimeMillis();
    }
    
    void bindShortcut(int keyCode, boolean longClick, Runnable callback) {
        if(longClick) {
            longClickKeyCodeMap.put(new Integer(keyCode), callback);
        } else {
            keyCodeMap.put(new Integer(keyCode), callback);
        }
    }
    
    DebugController.Script finishRecording() {
        DebugController.Script s = recording;
        recording = null;
        Display.getInstance().getCurrent().repaint();
        return s;
    }

    void playScript(DebugController.Script script) {
        gameKeyMap = script.startScript();
        script.execute(ratio);
        gameKeyMap = null;
    }
    
    DebugController.Script getCurrentRecording() {
        return recording;
    }

    /**
     * @inheritDoc
     */
    public boolean handleEDTException(Throwable err) {
        edtException = err;
        return true;
    }

    /**
     * @inheritDoc
     */
    protected  void keyPressed(final int keyCode){
        lastKeyCode = keyCode;
        pressTime = System.currentTimeMillis();
        pressCharged = true;
        if(recording != null) {
            recording.pushKeyEvent((int)(System.currentTimeMillis() - startTime), KEY_PRESSED, keyCode);
        }
        super.keyPressed(keyCode);
    }
    
    /**
     * Pauses a script vector 
     */
    void pauseScript() {
        pausedRecording = recording;
        recording = null;
        pauseDuration = System.currentTimeMillis();
    }

    void resumeScript() {
        recording = pausedRecording;
        pausedRecording = null;
        startTime += (System.currentTimeMillis() - pauseDuration);
    }
    
    private void revertLastPressFromScript() {
        if(recording != null) {
            recording.popKey();
        }
    }

    /**
     * @inheritDoc
     */
    protected  void keyReleased(final int keyCode){
        synchronized(longClickKeyCodeMap) {
            pressCharged = false;
        }
        if(!dontRecordNextRelease) {
            if(lastKeyCode == keyCode) {
                Runnable target = (Runnable)keyCodeMap.get(new Integer(keyCode));
                if(target != null) {
                    revertLastPressFromScript();
                    super.keyReleased(keyCode);
                    Display.getInstance().callSerially(target);
                    return;
                }
            }
            if(recording != null) {
                recording.pushKeyEvent((int)(System.currentTimeMillis() - startTime), KEY_RELEASED, keyCode);            
            }
        }
        dontRecordNextRelease = false;
        super.keyReleased(keyCode);
    }
    
    /**
     * @inheritDoc
     */
    protected  void pointerDragged(final int x, final int y){
        if(recording != null) {
            recording.pushPointerEvent((int)(System.currentTimeMillis() - startTime), POINTER_DRAGGED, x, y);            
        }
        super.pointerDragged(x, y);
    }
    
    /**
     * @inheritDoc
     */
    protected  void pointerPressed(final int x,final int y){
        if(recording != null) {
            recording.pushPointerEvent((int)(System.currentTimeMillis() - startTime), POINTER_DRAGGED, x, y);            
        }
        super.pointerPressed(x, y);
    }
    
    /**
     * @inheritDoc
     */
    protected  void pointerReleased(final int x,final int y){
        if(recording != null) {
            recording.pushPointerEvent((int)(System.currentTimeMillis() - startTime), POINTER_DRAGGED, x, y);            
        }
        super.pointerReleased(x, y);
    }

    /**
     * Indicates the behavior of LWUIT when an EDT violation is detected (not all
     * edt violations can be detected!) this can be one of: EDT_VIOLATION_IGNORE,
     * EDT_VIOLATION_RUNTIME_EXCEPTION, EDT_VIOLATION_WARN
     */
    int getEdtMode() {
        return edtMode;
    }

    /**
     * Indicates the behavior of LWUIT when an EDT violation is detected (not all
     * edt violations can be detected!) this can be one of: EDT_VIOLATION_IGNORE,
     * EDT_VIOLATION_RUNTIME_EXCEPTION, EDT_VIOLATION_WARN
     */
    void setEdtMode(int edtMode) {
        this.edtMode = edtMode;
    }

    /**
     * @inheritDoc
     */
    public void repaint(Animation cmp) {
        if(edtMode != DebugController.EDT_VIOLATION_IGNORE && (!Display.getInstance().isEdt())) {
            if(edtMode == DebugController.EDT_VIOLATION_RUNTIME_EXCEPTION) {
                throw new RuntimeException("EDT Violation Detected In Repaint Call For Component: " + cmp);
            }
            if(edtMode == DebugController.EDT_VIOLATION_WARN) {
                Log.p("EDT Violation Detected In Repaint Call For Component: " + cmp);
            }
        }
        super.repaint(cmp);
    }
    
    /**
     * @inheritDoc
     */
    protected void paintOverlay(Graphics g) {
        if(drawRecordingText) {
            if(recording != null) {
                g.translate(-g.getTranslateX(), -g.getTranslateY());
                g.setColor(0xff0000);
                g.fillArc(10, 10, 10, 10, 0, 360);
                g.setFont(Font.getDefaultFont());
                g.drawString("Recording", 23, 15 - (Font.getDefaultFont().getHeight() / 2));
            } else {
                if(pausedRecording != null) {
                    g.translate(-g.getTranslateX(), -g.getTranslateY());
                    g.setColor(0xff0000);
                    g.fillRect(10, 10, 3, 10);
                    g.fillRect(15, 10, 3, 10);
                    g.setFont(Font.getDefaultFont());
                    g.drawString("Paused", 23, 15 - (Font.getDefaultFont().getHeight() / 2));
                }
            }
        }
    }
}
